package cn.bugDance.infrastructure.persistent.repository;

import cn.bugDance.domain.rebate.model.aggregate.UserBehaviorRebateOrderAggregate;
import cn.bugDance.domain.rebate.model.entity.*;
import cn.bugDance.domain.rebate.model.valobj.BehaviorVO;
import cn.bugDance.domain.rebate.model.valobj.DallyBehaviorRebateStateVO;
import cn.bugDance.domain.rebate.model.valobj.RebateTypeVO;
import cn.bugDance.domain.rebate.repository.IRebateRepository;
import cn.bugDance.infrastructure.event.EventPublisher;
import cn.bugDance.infrastructure.persistent.dao.*;
import cn.bugDance.infrastructure.persistent.po.*;
import cn.bugDance.infrastructure.persistent.redis.IRedisService;
import cn.bugDance.types.common.Constants;
import cn.bugDance.types.enums.ResponseCode;
import cn.bugDance.types.exception.AppException;
import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBitSet;
import org.redisson.api.RLock;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Repository
@Slf4j
public class RebateRepository implements IRebateRepository {

    @Resource
    private IRedisService redisService;
    @Resource
    private IDallyBehaviorRebateDao dallyBehaviorRebateDao;
    @Resource
    private IUserBehaviorRebateOrderDao userBehaviorRebateOrderDao;
    @Resource
    private IDBRouterStrategy dbRouter;
    @Resource
    private EventPublisher eventPublisher;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private ITaskDao taskDao;
    @Resource
    private ThreadPoolExecutor threadPoolExecutor;
    @Resource
    private IUserCreditAccountDao userCreditAccountDao;
    @Resource
    private IUserCheckInOrderDao userCheckInOrderDao;
    @Resource
    private IUserCalendarSignRecordDao userCalendarSignRecordDao;
    @Resource
    private ICheckInConfigDao checkInConfigDao;

    @Override
    public boolean existDayCheckIn(String key, Date day) {
        String cacheKey = Constants.Redis.DAY_CHECK_IN + key;
        long expireMills = TimeUnit.DAYS.toMillis(1);
        return redisService.setNx(cacheKey, expireMills, TimeUnit.MILLISECONDS);
    }

    @Override
    public List<DallyBehaviorRebateEntity> queryBehaviorRebateEntities(String behaviorType) {
        List<DallyBehaviorRebate> behaviorRebates = dallyBehaviorRebateDao.getBehaviorRebates(behaviorType);
        if(null == behaviorRebates || behaviorRebates.isEmpty()) return null;

        List<DallyBehaviorRebateEntity> behaviorRebateEntities = new ArrayList<>();
        for (DallyBehaviorRebate behaviorRebate : behaviorRebates) {
            DallyBehaviorRebateEntity rebateEntity = new DallyBehaviorRebateEntity();

            rebateEntity.setBehaviorType(BehaviorVO.valueOf(behaviorType));
            rebateEntity.setRebateType(behaviorRebate.getRebateType());
            rebateEntity.setRebateConfig(behaviorRebate.getRebateConfig());
            rebateEntity.setRebateDesc(behaviorRebate.getRebateDesc());
            rebateEntity.setState(DallyBehaviorRebateStateVO.valueOf(behaviorRebate.getState()));

            behaviorRebateEntities.add(rebateEntity);
        }
        return behaviorRebateEntities;
    }

    @Override
    public void doSaveOrders(List<UserBehaviorRebateOrderAggregate> userBehaviorRebateOrderAggregates, BehaviorEntity behaviorEntity) {

    }


    @Override
    public Boolean isCalendarSignRebate(String key) {
        String cacheKey = Constants.Redis.DAY_CHECK_IN + key;
        return redisService.getValue(cacheKey) != null;
    }

    @Override
    public void creditRecharge(RebateCreditShopCartEntity rebateCreditShopCartEntity) {
        try{
            String userId = rebateCreditShopCartEntity.getUserId();
            String bizId = rebateCreditShopCartEntity.getBizId();
            Integer credit = rebateCreditShopCartEntity.getCredit();

            UserCreditAccount userCreditAccount = new UserCreditAccount();
            userCreditAccount.setUserId(userId);
            userCreditAccount.setTotalAmount(credit);
            userCreditAccount.setAvailableAmount(credit);

            dbRouter.doRouter(userId);
            transactionTemplate.execute(status -> {
                try{
                    int count = userCreditAccountDao.updateUserCreditAccount(userCreditAccount);
                    if(count <= 0){
                        userCreditAccountDao.insert(userCreditAccount);
                    }
                    return 0;
                }catch (DuplicateKeyException e){
                    log.warn("【返利仓储层-RebateRepository】: 插入积分订单主键冲突, userId: {}", userId);
                    status.setRollbackOnly();
                    throw new AppException(ResponseCode.DUPLICATE_KEY_EXCEPTION.getCode(), ResponseCode.DUPLICATE_KEY_EXCEPTION.getInfo());
                }
            });
        }finally {
            dbRouter.clear();
        }
    }

    @Override
    public Integer queryLongCheckInDay(String userId, Long activityId) {
        try{
            dbRouter.doRouter(userId);
            UserCheckInOrder userCheckInOrder = userCheckInOrderDao.queryUserCheckInOrderIntervalOneDay(userId, activityId, new Date());
        }finally {
            dbRouter.clear();
        }
        return 0;
    }

    @Override
    public UserCalendarSignRecordEntity queryPreviousSignDay(UserCalendarSignRecordEntity userCalendarSignRecordReqEntity) {
        try{
            String userId = userCalendarSignRecordReqEntity.getUserId();
            dbRouter.doRouter(userId);
            UserCalendarSignRecord userCalendarSignRecordReq = new UserCalendarSignRecord();
            userCalendarSignRecordReq.setUserId(userId);
            userCalendarSignRecordReq.setActivityId(userCalendarSignRecordReqEntity.getActivityId());
            userCalendarSignRecordReq.setSignDay(userCalendarSignRecordReqEntity.getSignDay());

            UserCalendarSignRecord userCalendarSignRecord = userCalendarSignRecordDao.getPreviousSignDay(userCalendarSignRecordReq);
            if(userCalendarSignRecord == null) return null;
            UserCalendarSignRecordEntity userCalendarSignRecordEntity = new UserCalendarSignRecordEntity();
            userCalendarSignRecordEntity.setUserId(userId);
            userCalendarSignRecordEntity.setActivityId(userCalendarSignRecordReqEntity.getActivityId());
            userCalendarSignRecordEntity.setLongSignDay(userCalendarSignRecord.getLongSignDay());
            userCalendarSignRecordEntity.setSignDay(userCalendarSignRecord.getSignDay());
            return userCalendarSignRecordEntity;
        }finally {
            dbRouter.clear();
        }
    }

    @Override
    public List<CheckInConfigEntity> queryCheckInConfig() {
        String cacheKey = Constants.Redis.CHECK_IN_CONFIG;
        List<CheckInConfigEntity> checkInConfigEntities = redisService.getValue(cacheKey);
        if(null != checkInConfigEntities) return checkInConfigEntities;

        List<CheckInConfig> checkInConfigs = checkInConfigDao.getCheckInConfig();
        if(checkInConfigs == null || checkInConfigs.isEmpty()) return null;
        checkInConfigEntities = new ArrayList<>();
        for (CheckInConfig checkInConfig : checkInConfigs) {
            CheckInConfigEntity checkInConfigEntity = new CheckInConfigEntity();
            checkInConfigEntity.setRebateConfig(checkInConfig.getRebateConfig());
            checkInConfigEntity.setRebateType(RebateTypeVO.valueOf(checkInConfig.getRebateType()));
            checkInConfigEntity.setLongCheckInDay(checkInConfig.getLongCheckInDay());
            checkInConfigEntities.add(checkInConfigEntity);
        }
        redisService.setValue(cacheKey, checkInConfigEntities);
        return checkInConfigEntities;
    }

    @Override
    public void saveCalendarSignRebateOrder(UserBehaviorRebateOrderAggregate userBehaviorRebateOrderAggregate) {
        String lockKey = Constants.Redis.USER_CHECK_IN_LOCK + userBehaviorRebateOrderAggregate.getUserId() + ":" + userBehaviorRebateOrderAggregate.getActivityId();
        RLock lock = redisService.getLock(lockKey);

        try{
            lock.lock();
            dbRouter.doRouter(userBehaviorRebateOrderAggregate.getUserId());
            UserCalendarSignRecordEntity userCalendarSignRecordEntity = userBehaviorRebateOrderAggregate.getUserCalendarSignRecordEntity();
            UserCalendarSignRecord userCalendarSignRecord = new UserCalendarSignRecord();
            userCalendarSignRecord.setUserId(userCalendarSignRecordEntity.getUserId());
            userCalendarSignRecord.setActivityId(userCalendarSignRecordEntity.getActivityId());
            userCalendarSignRecord.setBizId(userCalendarSignRecordEntity.getBizId());
            userCalendarSignRecord.setSignDay(userCalendarSignRecordEntity.getSignDay());
            userCalendarSignRecord.setSignMonth(userCalendarSignRecordEntity.getSignMonth());
            userCalendarSignRecord.setLongSignDay(userCalendarSignRecordEntity.getLongSignDay());
            userCalendarSignRecord.setIsRebate(userCalendarSignRecordEntity.getIsRebate());
            userCalendarSignRecord.setRebateType(userCalendarSignRecordEntity.getRebateType() == null ? null : userCalendarSignRecordEntity.getRebateType().getCode());
            userCalendarSignRecord.setRebateConfig(userCalendarSignRecordEntity.getRebateConfig());

            List<UserBehaviorRebateOrder> userBehaviorRebateOrders = new ArrayList<>();
            List<Task> tasks = new ArrayList<>();

            for(Map.Entry<UserBehaviorRebateOrderEntity, TaskEntity> entry : userBehaviorRebateOrderAggregate.getUserBehaviorRebateOrderPair().entrySet()){
                UserBehaviorRebateOrderEntity userBehaviorRebateOrderEntity = entry.getKey();
                UserBehaviorRebateOrder userBehaviorRebateOrder = new UserBehaviorRebateOrder();
                userBehaviorRebateOrder.setUserId(userBehaviorRebateOrderEntity.getUserId());
                userBehaviorRebateOrder.setOrderId(userBehaviorRebateOrderEntity.getOrderId());
                userBehaviorRebateOrder.setBizId(userBehaviorRebateOrderEntity.getBizId());
                userBehaviorRebateOrder.setRebateType(userBehaviorRebateOrderEntity.getRebateType());
                userBehaviorRebateOrder.setBehaviorType(userBehaviorRebateOrderEntity.getBehaviorType().getCode());
                userBehaviorRebateOrder.setRebateConfig(userBehaviorRebateOrderEntity.getRebateConfig());
                userBehaviorRebateOrder.setRebateDesc(userBehaviorRebateOrderEntity.getRebateDesc());

                userBehaviorRebateOrders.add(userBehaviorRebateOrder);

                TaskEntity taskEntity = entry.getValue();
                Task task = new Task();
                task.setUserId(taskEntity.getUserId());
                task.setTopic(taskEntity.getTopic());
                task.setState(taskEntity.getState().getCode());
                task.setMessage(JSON.toJSONString(taskEntity.getMessage()));
                task.setMessageId(taskEntity.getMessage().getId());

                tasks.add(task);

            }

            transactionTemplate.execute(status -> {
                try{
                    userCalendarSignRecordDao.insert(userCalendarSignRecord);
                    for(int i = 0; i < userBehaviorRebateOrders.size(); i++){
                        UserBehaviorRebateOrder userBehaviorRebateOrder = userBehaviorRebateOrders.get(i);
                        Task task = tasks.get(i);
                        userBehaviorRebateOrderDao.insert(userBehaviorRebateOrder);
                        taskDao.insert(task);
                    }
                    return 0;
                }catch (DuplicateKeyException e){
                    status.setRollbackOnly();
                    throw new AppException(ResponseCode.DUPLICATE_KEY_EXCEPTION.getCode(), ResponseCode.DUPLICATE_KEY_EXCEPTION.getInfo());
                }
            });
        }finally {
            lock.unlock();
            dbRouter.clear();
        }

        String cacheKey = Constants.Redis.USER_SIGN_MONTH_RECORD + userBehaviorRebateOrderAggregate.getUserId() + ":" + userBehaviorRebateOrderAggregate.getActivityId() + ":" + userBehaviorRebateOrderAggregate.getMonth();
        redisService.deleteKey(cacheKey);

        for(Map.Entry<UserBehaviorRebateOrderEntity, TaskEntity> entry : userBehaviorRebateOrderAggregate.getUserBehaviorRebateOrderPair().entrySet()) {
            threadPoolExecutor.execute(() -> {
                try{
                    dbRouter.doRouter(userBehaviorRebateOrderAggregate.getUserId());
                    eventPublisher.publish(entry.getValue().getTopic(), entry.getValue().getMessage());
                    taskDao.updateTaskStateSuccess(entry.getValue().getMessage().getId());
                }catch (Exception e){
                    taskDao.updateTaskStateFail(entry.getValue().getMessage().getId());
                }finally {
                    dbRouter.clear();
                }
            });
        }

    }

    @Override
    public List<UserCalendarSignRecordEntity> queryUserCalendarSignRecords(String userId, Long activityId, String month) {
        try{
            List<UserCalendarSignRecord> userCalendarSignRecords = userCalendarSignRecordDao.queryUserCalendarSignRecords(userId, activityId, month);
            if(userCalendarSignRecords == null) return null;
            List<UserCalendarSignRecordEntity> userCalendarSignRecordEntities = new ArrayList<>();
            for(UserCalendarSignRecord userCalendarSignRecord : userCalendarSignRecords){
                UserCalendarSignRecordEntity userCalendarSignRecordEntity = new UserCalendarSignRecordEntity();
                userCalendarSignRecordEntity.setUserId(userCalendarSignRecord.getUserId());
                userCalendarSignRecordEntity.setSignDay(userCalendarSignRecord.getSignDay());

                userCalendarSignRecordEntities.add(userCalendarSignRecordEntity);
            }
            return userCalendarSignRecordEntities;
        }finally {
            dbRouter.clear();
        }
    }
}
